use pest::Parser;
use pest_derive::Parser;
use std::{env, fs};

#[derive(Parser)]
#[grammar = "lexer.pest"]
struct SysYLexer;

fn get_line_number(input: &str, pos: usize) -> usize {
    input[..pos].chars().filter(|&c| c == '\n').count() + 1
}

fn convert_integer(text: &str) -> String {
    if text.starts_with("0x") || text.starts_with("0X") {
        let hex_str = &text[2..];
        u32::from_str_radix(hex_str, 16).unwrap_or(0).to_string()
    } else if text.starts_with("0")
        && text.len() > 1
        && text.chars().nth(1).unwrap().is_ascii_digit()
    {
        let oct_str = &text[1..];
        u32::from_str_radix(oct_str, 8).unwrap_or(0).to_string()
    } else {
        text.to_string()
    }
}

fn print_tokens(pairs: pest::iterators::Pairs<Rule>, input: &str) {
    for pair in pairs {
        for token in pair.into_inner() {
            if token.as_rule() == Rule::EOI {
                continue;
            }

            let rule_name = format!("{:?}", token.as_rule());
            let text = token.as_str();
            let line = get_line_number(input, token.as_span().start());

            let output_text = if token.as_rule() == Rule::INTEGER_CONST {
                convert_integer(text)
            } else {
                text.to_string()
            };

            eprintln!("{} {} at Line {}.", rule_name, output_text, line);
        }
    }
}

fn print_error(input: &str) {
    for (line_idx, line) in input.lines().enumerate() {
        for ch in line.chars() {
            if !ch.is_whitespace()
                && !ch.is_ascii_alphanumeric()
                && ch != '_'
                && !"(){}[];,=<>!&|+-*/%".contains(ch)
                && ch != '/'
            {
                eprintln!(
                    "Error type A at Line {}: Mysterious character \"{}\".",
                    line_idx + 1,
                    ch
                );
                return;
            }
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        eprintln!("Usage: {} <filename>", args[0]);
        std::process::exit(1);
    }

    let filename = &args[1];
    let input = fs::read_to_string(filename).expect("Failed to read file");

    match SysYLexer::parse(Rule::program, &input) {
        Ok(pairs) => print_tokens(pairs, &input),
        Err(_) => print_error(&input),
    }
}
